# -*- coding: utf-8 -*-
'''
Created on 2013-4-11

@author: cl.lam
'''
from datetime import datetime as dt
import traceback
import flask
from flask import current_app as app
from flask import g, render_template, flash, session, redirect, url_for, request, abort
from flask.blueprints import Blueprint
from sqlalchemy.sql.expression import and_, desc, or_
from flask.helpers import jsonify

from sys2do.views import BasicView
from sys2do.util.decorator import templated, loginRequired, mypaginate, \
    allPermission
from sys2do.util.logic_helper import getCurrentShopProfile, getCurrentShopID, \
    getCurrentUserID, checkLogin, makeUpdateLog, checkUpdate
from sys2do.model import db, qry, InventoryNote, InventoryProduct, InventoryLocation, Product, \
    Shop, InventoryNoteDtl, SysLog, SysFile, Item, InventoryNoteDtlItem, SysDict, DN
from sys2do.util.common import _g, _gld, multiupload, _gp, _gl
from sys2do.constant import IVTNT_APPROVE, IVTNT_DISAPPROVE, MSG_NO_SUCH_ACTION, \
    MESSAGE_ERROR, MSG_UPDATE_SUCC, MESSAGE_INFO, MSG_SERVER_ERROR, IVTNT_IN, \
    IVTNT_OUT, IVTNT_NEW, TNS_NEW, MSG_SAVE_SUCC, LOG_TYPE_REJECT, \
    LOG_TYPE_APPROVE, MSG_NO_ID_SUPPLIED, MSG_RECORD_NOT_EXIST, LOG_TYPE_UPDATE, \
    LOG_TYPE_CREATE, MSG_NOT_ENOUGH_PARAMS, IVTNT_INTERNAL, ITEM_AVAILABLE, \
    ITEM_LOCKED, ACTIVE, INACTIVE, IVTNT_TO_WASTAGE, ITEM_UNAVAILABLE

from sys2do.util.exception import NoInventoryProvidedExp, NoRelatedItemExp, ItemQtyNotMatchExp, \
    ProdutItemNotMatchExp, ItemNoInCorrectInventoryExp, CustomizedExp, \
    ItemAlreadyoccupiedExp
from sys2do.model.logic import InventoryNote



__all__ = ['bpIvtnt']


bpIvtnt = Blueprint( 'bpIvtnt', __name__, )

@bpIvtnt.before_request
def _b():
    return checkLogin()

class InventoryNoteView( BasicView ):

    template_folder = 'ivtnt'

    @templated( "index.html" )
    @mypaginate( 'result' )
    @allPermission( ['INVENTORY_NOTE_VIEW'] )
    def index( self ):
        if request.method == 'POST':
            form = SrhForm( request.form )
            session[url_for( '.view' )] = form
        else:
            form = session.get( url_for( '.view' ), SrhForm( request.form ) )
        form.url = url_for( '.view' )

        spro = getCurrentShopProfile()
        cds = [ InventoryNote.active == 0, InventoryNote.shopID == spro.shopID ]


        if form.no.data : cds.append( InventoryNote.no.like( '%%%s%%' % form.no.data ) )
        if form.refer.data : cds.append( InventoryNote.refer.like( '%%%s%%' % form.refer.data ) )
        if form.createTimeFrom.data : cds.append( InventoryNote.createTime > form.createTimeFrom.data )
        if form.createTimeTo.data : cds.append( InventoryNote.createTime < form.createTimeTo.data )

        if form.direction.data and form.direction.data != 'None' :  cds.append( InventoryNote.direction == form.direction.data )
        if form.status.data and form.status.data != 'None' :  cds.append( InventoryNote.status == form.status.data )
        result = InventoryNote.iall( conditions = cds, order_by = desc( InventoryNote.sysCreateTime ) )
        return {'form' : form, 'result' : result}


    @templated( "view.html" )
    def view( self ):
        spobj = getCurrentShopProfile()
        obj = InventoryNote.get( _g( 'id' ) )
        ivts = []

        if obj.direction == IVTNT_IN and obj.approvable():
            spobj = getCurrentShopProfile()
            root = spobj.inventory
            ivts = [root, ]
            ivts.extend( [c for c in root.getChildren()] )

        canReject = True
        if obj.status != IVTNT_NEW : canReject = False
        else :
            source = obj.comeFrom
            # if the inventory note(A) is created by other inventory note(B),
            # and B's shop is not equal to A ,that's meaning A is created because B is reject
            while source and isinstance( source, InventoryNote ):
                if source.status == IVTNT_DISAPPROVE and source.shopID != spobj.shopID :
                    canReject = False
                    break
                source = source.comeFrom

        return {'obj' : obj, 'ivts' : ivts , 'canReject' : canReject}


    '''
    @templated( "add.html" )
    def add( self ):
        spobj = getCurrentShopProfile()

        if request.method == 'GET':
            t = _g( 't' )
            if t not in [IVTNT_IN, IVTNT_OUT]:
                flash( MSG_NO_SUCH_ACTION, MESSAGE_ERROR )
                return redirect( url_for( '.view' ) )

            if t == IVTNT_IN:
                sdIvts = qry( InventoryLocation ).filter( InventoryLocation.name.in_( ['IN_TRAVEL', 'SOLD_OUT',
                                                                                   ] ) ).order_by( InventoryLocation.id )
                return render_template( 'ivtnt/add_in.html', source = sdIvts )
            else:
                sdIvts = qry( InventoryLocation ).filter( InventoryLocation.name.in_( ['IN_TRAVEL', 'SOLD_OUT', 'WASTAGE',
                                                                                   ] ) ).order_by( InventoryLocation.id )
                ivts = InventoryLocation.iall( conditions = [InventoryLocation.active == 0,
                                                      InventoryLocation.referID == spobj.shopID], order_by = InventoryLocation.name )
                return render_template( 'ivtnt/add_out.html' , ivts = ivts, dest = sdIvts )

        if request.method == 'POST':
            params = _gld( 'ivtID', 'direction', 'refer', 'remark', 'createTime' )
            params['shopID'] = spobj.shopID
            params['status'] = IVTNT_NEW
            params['attachment'] = multiupload()
            try:
                hdr = InventoryNote.create( params )
                db.add( hdr )
                for k, v in _gp( 'qty_' ):
                    n, pid, vid = k.split( "_" )
                    qty = v
                    remark = _g( 'remark_%s_%s' % ( pid, vid ) )
                    if not v or not qty : continue
                    if hdr.direction == IVTNT_IN:
                        dtl = InventoryNoteDtl.create( {'hdr' : hdr, 'pdtID' : pid, 'qty' : qty ,
                                                      'sIvtLtnID' : hdr.ivtID, 'dIvtLtnID' : None, 'remark' : remark } )
                    else:
                        dtl = InventoryNoteDtl.create( {'hdr' : hdr, 'pdtID' : pid, 'qty' : qty ,
                                                      'sIvtLtnID' : vid, 'dIvtLtnID' : hdr.ivtID, 'remark' : remark } )

                    pdt = Product.get( pid )
                    dtl.copyPdtInfo( pdt )
                    db.add( dtl )
                db.add( SysLog( refClz = hdr.__class__.__name__, type = LOG_TYPE_CREATE, refID = hdr.id, ) )
                db.commit()
                flash( MSG_SAVE_SUCC, MESSAGE_INFO )
            except:
                self._error( traceback.format_exc() )
                flash( MSG_SERVER_ERROR, MESSAGE_ERROR )
                db.rollback()
                return redirect( url_for( '.view' ) )
            else:
                return redirect( url_for( '.view', action = 'view', id = hdr.id ) )
    '''




    @templated( 'update.html' )
#     @allPermission( 'INVENTORY_NOTE_EDIT' )
    @loginRequired
    def update( self ):
        nid = _g( 'id' )
        if not nid :
            flash( MSG_NO_ID_SUPPLIED, MESSAGE_ERROR )
            return redirect( url_for( '.view' ) )
        obj = InventoryNote.get( nid )
        if not obj :
            flash( MSG_RECORD_NOT_EXIST, MESSAGE_ERROR )
            return redirect( url_for( '.view' ) )

        if request.method != 'POST':
            sdIvts = qry( InventoryLocation ).filter( InventoryLocation.name.in_( ['IN_TRAVEL', 'SOLD_OUT', 'WASTAGE', 'PURCHASE', 'PROCESS'
                                                                                   ] ) ).order_by( InventoryLocation.id )

            items = {}

            if obj.direction == IVTNT_IN:
                for d in obj.dtls :
                    items[d.pdtno] = qry( Item ).filter( and_( Item.ivtID == d.sIvtLtnID, Item.pdtID == d.pdtID,
                                                               or_( Item.status == ITEM_AVAILABLE, and_( Item.status == ITEM_LOCKED, Item.refer == obj.no ) )
                                                                ) ).order_by( Item.id )
                return render_template( 'ivtnt/update_in.html', obj = obj, source = sdIvts , items = items )

            else:
                data = []
                for d in obj.dtls:
                    cs = [InventoryLocation.id == InventoryProduct.ivtID,
                          InventoryProduct.ivtID == d.sIvtLtnID,
                          InventoryProduct.pdtID == d.pdtID
                          ]
                    ( ivt, ivtip ) = qry( InventoryLocation, InventoryProduct ).filter( and_( *cs ) ).one()
                    data.append( ( d, ivt, ivtip ) )

                    key = "%s_%s" % ( d.pdtID, d.sIvtLtnID )
                    tmp = qry( Item ).filter( and_( Item.ivtID == d.sIvtLtnID, Item.pdtID == d.pdtID,
                                                    or_( Item.status == ITEM_AVAILABLE, and_( Item.status == ITEM_LOCKED, Item.refer == obj.no ) )
                                                     ) ).order_by( Item.id ).all()
                    if key not in items : items[key] = tmp
                    else : items[key].extend( tmp )
                return render_template( 'ivtnt/update_out.html', obj = obj, data = data, dest = sdIvts, items = items )

        try:
            oldCopy = obj.serialize()
            extLog = []

            params = _gld( 'remark', 'createTime' )
            obj.update( params )

            oldaset = set( map( lambda v : unicode( v.id ), obj.attachment ) )
            newaset = set( _g( 'attachment', '' ).split( "|" ) )
            deltedAtm = oldaset.difference( newaset )
            if deltedAtm :
                delAtms = qry( SysFile ).filter( SysFile.id.in_( list( deltedAtm ) ) )
                extLog.extend( [u'删除附件[%s]。' % da.name for da in delAtms] )

            if newaset : obj.attachment = sorted( list( newaset ) ) + multiupload()
            else : obj.attachment = multiupload()

            #===================================================================
            # release the locked items
            #===================================================================
            for item in qry( Item ).filter( Item.no.in_( obj.itemList ) ):
                if item.status == ITEM_LOCKED :
                    if item.refer != obj.no : raise ItemAlreadyoccupiedExp()
                    item.status, item.refer = ITEM_AVAILABLE, None


            for d in obj.dtls:
                oldDtl = d.serialize()
                d.remark = _g( 'remark_%s' % d.id )

                '''
                if obj.direction == IVTNT_IN: d.sIvtLtnID = obj.ivtID
                else : d.dIvtLtnID = obj.ivtID
                '''

                newDtl = d.serialize()
                tmpLog = checkUpdate( oldDtl, newDtl )
                if tmpLog : extLog.extend( [u'修改商品 [%s]：' % d.pdtname, ] + tmpLog )
                cbName = 'item_%s_%s' % ( d.pdtID, d.sIvtLtnID )
                d.itemList = _gl( cbName ) or None

            #===================================================================
            # lock the new items
            #===================================================================
            if obj.itemList:
                for item in qry( Item ).filter( Item.no.in_( obj.itemList ) ):
                    if item.status != ITEM_AVAILABLE : raise ItemAlreadyoccupiedExp()
                    item.status, item.refer = ITEM_LOCKED, obj.no

            newCopy = obj.serialize()
            makeUpdateLog( obj, oldCopy, newCopy, extLog )
            flash( MSG_SAVE_SUCC, MESSAGE_INFO )
            db.commit()
        except:
            self._error( traceback.format_exc() )
            flash( MSG_SERVER_ERROR, MESSAGE_ERROR )
            db.rollback()
            return redirect( url_for( '.view' ) )
        else:
            return redirect( url_for( '.view', action = 'view', id = obj.id ) )



    def app( self ):
        spobj = getCurrentShopProfile()
        obj = InventoryNote.get( _g( 'id' ) )
        if not obj :
            flash( MSG_RECORD_NOT_EXIST, MESSAGE_ERROR )
            return redirect( url_for( '.view' ) )

        flag = _g( 'flag' )
        if flag not in map( unicode, [IVTNT_APPROVE, IVTNT_DISAPPROVE] ):
            flash( MSG_NO_SUCH_ACTION, MESSAGE_ERROR )
            return redirect( url_for( '.view' ) )
        try:
            if obj.direction == IVTNT_IN :    # IN
                obj.status = IVTNT_APPROVE
                obj.appTime = dt.now()
                for d in obj.dtls:
                    destID = int( _g( 'ivt_%s' % d.id , 0 ) )
                    if not destID : raise NoInventoryProvidedExp()

                    InventoryProduct.toMove( d.sIvtLtnID, d.dIvtLtnID, d.pdtID, d.qty )
                    if d.dIvtLtnID != destID :    # if the receive ivt is not the actual ivt, move it from root ivt to the actual ivt
#                         InventoryProduct.goingToMove( d.dIvtLtnID, destID, d.pdtID, d.qty )
                        InventoryProduct.toMove( d.dIvtLtnID, destID, d.pdtID, d.qty )
                        d.dIvtLtnID = destID

                #=======================================================
                # attatch the item to the inventory ,for item tracing
                #=======================================================
                self.attatchItem( obj )
                db.add( SysLog( refClz = obj.__class__.__name__, type = LOG_TYPE_APPROVE, refID = obj.id, remark = u'确认入库' ) )

            else:    # OUT
                obj.status = IVTNT_APPROVE
                obj.appTime = dt.now()
                for d in obj.dtls: InventoryProduct.toMove( d.sIvtLtnID, d.dIvtLtnID, d.pdtID, d.qty )
                #=======================================================
                # attatch the item to the inventory ,for item tracing
                #=======================================================
                self.attatchItem( obj )
                db.add( SysLog( refClz = obj.__class__.__name__, type = LOG_TYPE_APPROVE, refID = obj.id, remark = u'确认出库' ) )
            #===========================================================
            # release the items, set the status to be available
            #===========================================================
            qry( Item ).filter( and_( Item.no.in_( obj.itemList ) ) ).update( {'status' : ITEM_AVAILABLE, 'refer' : None}, synchronize_session = 'fetch' )

            db.commit()
            flash( MSG_UPDATE_SUCC, MESSAGE_INFO )
        except CustomizedExp as e:
            self._error( traceback.format_exc() )
            db.rollback()
            flash( unicode( e ), MESSAGE_ERROR )
        except:
            self._error( traceback.format_exc() )
            db.rollback()
            flash( MSG_SERVER_ERROR, MESSAGE_ERROR )

        return redirect( url_for( '.view', action = 'view', id = obj.id ) )



    def disapp( self ):
        obj = InventoryNote.get( _g( 'id' ) )
        if not obj :
            flash( MSG_RECORD_NOT_EXIST, MESSAGE_ERROR )
            return redirect( url_for( '.view' ) )

        try:

            obj.status = IVTNT_DISAPPROVE
            #=======================================================================
            # 1.move back to the IN/OUT qty from the dest to the source
            # 2.release the item locked by the note
            #=======================================================================
#             for d in obj.dtls :
#                 InventoryProduct.goingToMove( d.sIvtLtnID, d.dIvtLtnID, d.pdtID, d.qty * -1 )
#                 d.releaseItems()

            remark = u''
            isDN = bool( obj.refer ) and SysDict.getName( 'OBJECT_PREFIX', obj.refer[:3] ) == 'DN'
            if obj.direction == IVTNT_IN :    # IN
                remark = u'拒绝收货'

                if isDN :
                    #===========================================================
                    # check the related item
                    #===========================================================
                    if len( list( obj.itemList ) ) != sum( map( lambda d : d.qty, obj.dtls ) ) : raise ItemQtyNotMatchExp()
                    for d in obj.dtls:
                        if not d.itemList : raise NoRelatedItemExp()
                        for item in qry( Item ).filter( Item.no.in_( d.itemList ) ):
                            if item.status != ITEM_LOCKED or item.refer != obj.no : raise ProdutItemNotMatchExp()
                            # release the item
                            item.status, item.refer = ITEM_AVAILABLE, None
                    #===================================================================
                    # create the IN note for the source shop
                    #===================================================================
                    itivt = InventoryLocation.getInTravelIvt()
                    dnhdr = qry( DN ).filter( DN.no == obj.refer ).one()
                    dnpro = dnhdr.sShop.getProfile()

                    hdr = InventoryNote.create( {'shopID' : dnhdr.sShopID, 'direction' : IVTNT_IN, 'ivtID' : itivt.id,
                                                 'type' : 'TNS', 'refer' : obj.refer, } )
                    hdr.comeFrom = ( obj, obj.id )
                    db.add( hdr )
                    for d in obj.dtls :
                        dtl = InventoryNoteDtl.create( {'hdr' : hdr, 'pdtID' : d.pdtID, 'qty' : d.qty,
                                                        'sIvtLtnID' : itivt.id, 'dIvtLtnID' : dnpro.inventoryID} )
                        dtl.copyPdtInfo( d.pdt )
                        db.add( dtl )
#                         InventoryProduct.goingToMove( dtl.sIvtLtnID, dtl.dIvtLtnID, dtl.pdtID, dtl.qty )

            elif obj.direction == IVTNT_OUT:
                remark = u'取消发货'
                if isDN :
                    dnhdr = qry( DN ).filter( DN.no == obj.refer ).one()
                    dnpro = dnhdr.dShop.getProfile()

                    for d in obj.dtls:
                        d.releaseItems()
                        #===================================================================
                        # minus the qty from the dest shop's IN note,and cancel the order if
                        # the qty is 0
                        #===================================================================
                        tmp = qry( InventoryNote, InventoryNoteDtl ).filter( and_( 
                                                                        InventoryNote.active == ACTIVE, InventoryNoteDtl.active == ACTIVE,
                                                                        InventoryNote.id == InventoryNoteDtl.hdrID,
                                                                        InventoryNote.shopID == dnpro.inventoryID,
                                                                        InventoryNote.direction == IVTNT_IN, InventoryNote.status == IVTNT_NEW,
                                                                        InventoryNote.refer == obj.refer,
                                                                        InventoryNoteDtl.pdtID == d.pdtID,
                                                                        ) ).order_by( InventoryNote.id )
                        originalQty = d.qty
                        for ( hdr, dtl ) in tmp :
                            if dtl.qty >= originalQty :
                                dtl.qty -= originalQty
                                if dtl.qty == 0 :
                                    dtl.active = INACTIVE
                                    if len( hdr.dtls ) == 0 : hdr.active = INACTIVE
#                                 InventoryProduct.goingToMove( dtl.sIvtLtnID, dtl.dIvtLtnID, dtl.pdtID, originalQty * -1 )
                                break
                            else:
                                originalQty -= dtl.qty
                                dtl.active = INACTIVE
                                if len( hdr.dtls ) == 0 : hdr.active = INACTIVE
#                                 InventoryProduct.goingToMove( dtl.sIvtLtnID, dtl.dIvtLtnID, dtl.pdtID, dtl.qty * -1 )


            db.add( SysLog( refClz = obj.__class__.__name__, type = LOG_TYPE_REJECT, refID = obj.id, remark = remark ) )
            db.commit()
            flash( MSG_UPDATE_SUCC, MESSAGE_INFO )
        except CustomizedExp as e:
            self._error( traceback.format_exc() )
            db.rollback()
            flash( unicode( e ), MESSAGE_ERROR )
        except:
            self._error( traceback.format_exc() )
            db.rollback()
            flash( MSG_SERVER_ERROR, MESSAGE_ERROR )
        return redirect( url_for( '.view', action = 'view', id = obj.id ) )



    @templated( 'view_log.html' )
    def viewLog( self ):
        id = _g( 'id' )
        if not id : abort( 404 )
        obj = InventoryNote.get( id )
        if not obj : abort( 404 )
        return {'obj' : obj}



    def ajaxSrhPdt( self ):
        f = _g( 'f' )
        q = _g( 'q' )
        t = _g( 'direction' )

        if not f : return jsonify( {'code' : 1, 'msg' : MSG_NOT_ENOUGH_PARAMS, 'data' : []} )
        if t not in [IVTNT_IN, IVTNT_OUT] : return jsonify( {'code' : 1 , 'msg' : MSG_NO_SUCH_ACTION} )

        try:
            if t == IVTNT_IN:
                cs = [Product.active == 0, ]
                if f : cs.append( getattr( Product, f ).like( '%%%s%%' % q ) )
                tvlivt = InventoryLocation.getInTravelIvt()
                data = []

                for p in Product.all( conditions = cs, order_by = Product.name ):
                    info = {'id' : p.id, 'no' : p.no, 'name' : p.name, 'desc' : p.makeDesc(), }
                    info.update( {'pdivtid' : '%s_%s' % ( p.id, tvlivt.id ), 'ivtid' : tvlivt.id, 'ivtname' : ''} )
                    data.append( info )
            else:
                spobj = getCurrentShopProfile()
                cs = [Product.active == 0, InventoryLocation.active == 0, InventoryLocation.referID == spobj.shopID,
                      InventoryProduct.pdtID == Product.id, InventoryProduct.ivtID == InventoryLocation.id,
                      InventoryProduct.availableQty > 0, ]
                if f : cs.append( getattr( Product, f ).like( '%%%s%%' % q ) )
                result = qry( Product, InventoryLocation, InventoryProduct ).filter( and_( *cs ) ).order_by( Product.no )
                '''
                data = []
                for pdt, ivt, ip in result:
                    info = pdt.toJson()
                    info.update( {'pdivtid' : '%s_%s' % ( pdt.id, ivt.id ) , 'ivtid' : ivt.id, 'ivtname' : ivt.fullPath, 'qty' : ip.qty } )
                    data.append( info )
                '''

                data = {}
                for ( pdt, ivt, ip ) in result :
                    if pdt.id not in data : data[pdt.id] = {
                                                            'pid' : pdt.id, 'no' : pdt.no, 'name' : pdt.name,
                                                            'desc' : pdt.makeDesc(),
                                                            'ivts' : [( ivt.id, unicode( ivt.fullPath ), ip.availableQty ), ] }
                    else: data[pdt.id]['ivts'].append( ( ivt.id, unicode( ivt.fullPath ), ip.availableQty ) )
            return jsonify( {
                            'code' : 0 , 'msg' : 'OK',
                            'data' : data,
                            } )
        except:
            self._error( traceback.format_exc() )
            return jsonify( {'code' : 1, 'msg' : MSG_SERVER_ERROR} )



    @templated( 'split.html' )
    def split( self ):
        iid = _g( 'id' )
        if not iid :
            flash( MSG_NO_ID_SUPPLIED, MESSAGE_ERROR )
            return redirect( url_for( '.view' ) )

        obj = InventoryNote.get( iid )
        if not obj :
            flash( MSG_NO_ID_SUPPLIED, MESSAGE_ERROR )
            return redirect( url_for( '.view' ) )

        if not obj.splitable() :
            flash( MSG_NO_SUCH_ACTION, MESSAGE_ERROR )
            return redirect( url_for( '.view', action = 'view', id = obj.id ) )

        #=======================================================================
        # view page
        #=======================================================================
        if request.method != 'POST':
            sdIvts = qry( InventoryLocation ).filter( InventoryLocation.name.in_( ['IN_TRAVEL', 'SOLD_OUT', 'WASTAGE',
                                                                                   ] ) ).order_by( InventoryLocation.id )

            items = {}

            if obj.direction == IVTNT_IN:
                for d in obj.dtls :  items[d.pdtno] = qry( Item ).filter( and_( Item.ivtID == d.sIvtLtnID, Item.pdtID == d.pdtID,
                                                                                or_( Item.status == ITEM_AVAILABLE, and_( Item.status == ITEM_LOCKED, Item.refer == obj.no ) )
                                                                                ) ).order_by( Item.id )
                return render_template( 'ivtnt/split_in.html', obj = obj, source = sdIvts, items = items )
            else:
                data = []
                for d in obj.dtls:
                    cs = [InventoryLocation.id == InventoryProduct.ivtID,
                          InventoryProduct.ivtID == d.sIvtLtnID,
                          InventoryProduct.pdtID == d.pdtID
                          ]
                    ( ivt, ivtip ) = qry( InventoryLocation, InventoryProduct ).filter( and_( *cs ) ).one()
                    data.append( ( d, ivt, ivtip ) )

                    key = "%s_%s" % ( d.pdtID, d.sIvtLtnID )
                    tmp = qry( Item ).filter( and_( Item.ivtID == d.sIvtLtnID, Item.pdtID == d.pdtID,
                                                    or_( Item.status == ITEM_AVAILABLE, and_( Item.status == ITEM_LOCKED, Item.refer == obj.no ) )
                                                    ) ).order_by( Item.id ).all()
                    if key not in items : items[key] = tmp
                    else: items[key].extend( tmp )

                return render_template( 'ivtnt/split_out.html', obj = obj, data = data, dest = sdIvts, items = items )

        #=======================================================================
        # save page
        #=======================================================================
        try:
            params = _gld( 'remark', 'createTime' )
            params['ivtID'] = obj.ivtID
            params['shopID'] = obj.shopID
            params['direction'] = obj.direction
            params['refer'] = obj.refer
            params['type'] = obj.type
#             params['itemList'] = _gl( 'items' )
            newobj = InventoryNote.create( params )
            newobj.attachment = multiupload()
            newobj.comeFrom = ( obj, obj.id )

            for d in obj.dtls:
                if not _g( 'oldqty_%s' % d.id ) : continue
                dparam = {'hdr' : newobj, 'pdtID' : d.pdtID, 'sIvtLtnID' : d.sIvtLtnID, 'dIvtLtnID' : d.dIvtLtnID}
                dparam['remark'] = _g( 'oldremark_%s' % d.id )
                dparam['qty'] = _g( 'oldqty_%s' % d.id )
                cbName = 'item_%s_%s' % ( d.pdtID, d.sIvtLtnID )
                dparam['itemList'] = _gl( cbName ) or None

                try:
                    qty = int( _g( 'oldqty_%s' % d.id ) )    # if the transfer is error
                    if qty <= 0 : continue    # no qty ,don't precess
                    d.qty -= qty
                    if d.qty <= 0 : d.active = 1    # if split all the qty ,inactive the orignal detail
                    newDtl = InventoryNoteDtl.create( dparam )
                    db.add( newDtl )
                    newDtl.copyPdtInfo( d.pdt )
                    #===========================================================
                    # remove the items already exist in the old dtl
                    #===========================================================
                    d.itemList = sorted( list( set( d.itemList ).difference( newDtl.itemList ) ) )
                except:
                    self._error( traceback.format_exc() )
                    pass

            #===================================================================
            # lock the items to the split inventory note
            #===================================================================
            if newobj.itemList:
                qry( Item ).filter( and_( Item.no.in_( newobj.itemList ) ) ).update( {'status' : ITEM_LOCKED, 'refer' : newobj.no}, synchronize_session = 'fetch' )

            db.flush()
            db.add( SysLog( refClz = obj.__class__.__name__, type = LOG_TYPE_UPDATE, refID = obj.id, remark = u'分拆出 [%s]。' % obj.no ) )
            db.add( SysLog( refClz = newobj.__class__.__name__, type = LOG_TYPE_CREATE, refID = newobj.id, remark = u'从 [%s] 分拆。' % obj.no ) )
            db.commit()
            flash( MSG_SAVE_SUCC, MESSAGE_INFO )
        except:
            self._error( traceback.format_exc() )
            flash( MSG_SERVER_ERROR, MESSAGE_ERROR )
            db.rollback()
            return redirect( url_for( '.view' ) )
        else:
            return redirect( url_for( '.view', action = 'view', id = newobj.id ) )



    def attatchItem( self, hdr ):
        if not hdr.itemList : raise NoRelatedItemExp()
        if len( filter( bool, hdr.itemList ) ) != sum( [d.qty for d in hdr.dtls] ) : raise ItemQtyNotMatchExp()

        items = qry( Item ).filter( and_( Item.no.in_( hdr.itemList ) ) )
        mapping = {}
        for item in items :
            key = '%s_%s' % ( item.pdtID, item.ivtID )
            if key not in mapping: mapping[key] = [item]
            else : mapping[key].append( item )

        for d in hdr.dtls:
            key = '%s_%s' % ( d.pdtID, d.sIvtLtnID )
            if key not in mapping : raise ProdutItemNotMatchExp()
            for x in range( d.qty ):
                item = mapping[key].pop()
                if item.ivtID != d.sIvtLtnID : raise ItemNoInCorrectInventoryExp()
                item.ivtID = d.dIvtLtnID    # move the item to the dtl's dest inventory
                db.add( InventoryNoteDtlItem.create( {'hdr' : hdr, 'dtl' : d, 'itemID' : item.id} ) )

        for v in mapping.values():    # make the final check, if there's left item ,then raise exception
            if v : raise ItemQtyNotMatchExp()



    def turnToWastage( self ):
        iid = _g( 'id' )
        if not iid :
            flash( MSG_NO_ID_SUPPLIED, MESSAGE_ERROR )
            return redirect( url_for( '.view' ) )

        obj = InventoryNote.get( iid )
        if not obj :
            flash( MSG_RECORD_NOT_EXIST, MESSAGE_ERROR )
            return redirect( url_for( '.view' ) )

        if obj.status != IVTNT_NEW:
            flash( MSG_NO_SUCH_ACTION, MESSAGE_ERROR )
            return redirect( url_for( '.view' ) )

        try:
            obj.status = IVTNT_TO_WASTAGE
            wIvt = InventoryLocation.getWastageIvt()
            for d in obj.dtls :
                #===================================================================
                # 1.cancel the note dtl's orignal dest inventory
                # 2.change the note dtl's dest to the wastage inventory
                # 3.move the qty to the wastage
                # 4.move the related items to the wastage
                #===================================================================
                if len( d.itemList ) != d.qty : raise ItemQtyNotMatchExp()
                for item in qry( Item ).filter( Item.no.in_( d.itemList ) ):
                    if item.ivtID != d.sIvtLtnID : raise ItemNoInCorrectInventoryExp()
                    if item.status != ITEM_LOCKED or item.refer != obj.no : raise ProdutItemNotMatchExp()
                    item.ivtID, item.status = wIvt.id, ITEM_UNAVAILABLE

#                 InventoryProduct.goingToMove( d.sIvtLtnID, d.dIvtLtnID, d.pdtID, -1 * d.qty )
                d.dIvtLtnID = wIvt.id
#                 InventoryProduct.goingToMove( d.sIvtLtnID, d.dIvtLtnID, d.pdtID, d.qty )
                InventoryProduct.toMove( d.sIvtLtnID, d.dIvtLtnID, d.pdtID, d.qty )

            db.add( SysLog( refClz = obj.__class__.__name__, type = LOG_TYPE_UPDATE, refID = obj.id, remark = u'转为损耗，原因：%s' % obj.remark ) )
            db.commit()
            flash( MSG_UPDATE_SUCC, MESSAGE_INFO )
        except CustomizedExp as e:
            self._error( traceback.format_exc() )
            db.rollback()
            flash( unicode( e ), MESSAGE_ERROR )
        except:
            self._error( traceback.format_exc() )
            db.rollback()
            flash( MSG_SERVER_ERROR, MESSAGE_ERROR )
        return redirect( url_for( '.view', action = 'view', id = obj.id ) )


bpIvtnt.add_url_rule( '/', view_func = InventoryNoteView.as_view( 'view' ), defaults = {'action':'index'} )
bpIvtnt.add_url_rule( '/<action>', view_func = InventoryNoteView.as_view( 'view' ) )


#===============================================================================
# form class
#===============================================================================

from wtforms import Form, TextField, SelectField
from sys2do.util.wt_helper import MyDateField


class SrhForm( Form ):
    no = TextField( u'系统编号', )
    direction = SelectField( u'出/入库', choices = [( '', '' ), ( IVTNT_IN, u'入库' ), ( IVTNT_OUT, u'出库' ), ( IVTNT_INTERNAL, u'内部转仓' )] )
    refer = TextField( u'涉及', )
    status = SelectField( u'状态', choices = [( '', '' ), ( IVTNT_NEW, u'新建' ), ( IVTNT_APPROVE, u'批准' ),
                                            ( IVTNT_DISAPPROVE, u'拒绝' ), ( IVTNT_TO_WASTAGE, u'损耗' )] )
    createTimeFrom = MyDateField( u'开单时间(开始)' )
    createTimeTo = MyDateField( u'开单时间(结束)' )

